#include "Font.h"
#include "ResourceManager.h"

using namespace GalliumEngine;

Font::Font()
{
	loaded = false;

	params.push_back(BindParams("coord",BINDTYPE_ATTRIBUTE));
	params.push_back(BindParams("uv",BINDTYPE_ATTRIBUTE));
	params.push_back(BindParams("textureSampler",BINDTYPE_UNIFORM));
	params.push_back(BindParams("colour",BINDTYPE_UNIFORM));
	params.push_back(BindParams("mvp",BINDTYPE_UNIFORM));

	shader = ResourceManager::getInstance()->loadShader("FontShader",
		"Resources/Shaders/Font2Dvert.glsl","",
		"Resources/Shaders/Font2Dfrag.glsl",
		params);
}

Font::~Font()
{
}

inline int next_p2(int n)
{
	int res = 1;

	while(res < n)
	{
		res <<= 1;
	}

	return res;
}

void Font::bind()
{
	glGenBuffers(1, &vertexbuffer);
	glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
	glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vector3), &vertices[0], GL_STATIC_DRAW);

	glGenBuffers(1, &uvbuffer);
	glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
	glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(Vector2), &uvs[0], GL_STATIC_DRAW);
}

void Font::createChar(int iIndex)
{
	FT_Load_Glyph(ftFace, FT_Get_Char_Index(ftFace, iIndex), FT_LOAD_DEFAULT);

	FT_Render_Glyph(ftFace->glyph, FT_RENDER_MODE_NORMAL);
	FT_Bitmap* pBitmap = &ftFace->glyph->bitmap;

	int iW = pBitmap->width;
	int iH = pBitmap->rows;
	int iTW = next_p2(iW);
	int iTH = next_p2(iH);

	GLubyte* bData = new GLubyte[iTW*iTH];
	// Copy glyph data and add dark pixels elsewhere
	for(int ch = 0; ch < iTH; ch++)
	{
		for(int cw = 0; cw < iTW; cw++)
		{
			bData[ch*iTW+cw] = (ch >= iH || cw >= iW) ? 0 : pBitmap->buffer[(iH-ch-1)*iW+cw];
		}
	}
 
	// And create a texture from it
	tCharTextures[iIndex].createFromData(bData, iTW, iTH, 8, GL_DEPTH_COMPONENT, false);
	tCharTextures[iIndex].setFiltering(TextureFiltering::TEXTURE_FILTER_MAG_BILINEAR, TextureFiltering::TEXTURE_FILTER_MIN_BILINEAR);

	tCharTextures[iIndex].setSamplerParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	tCharTextures[iIndex].setSamplerParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	// Calculate glyph data
	iAdvX[iIndex] = ftFace->glyph->advance.x >> 6;
	iBearingX[iIndex] = ftFace->glyph->metrics.horiBearingX >> 6;
	iCharWidth[iIndex] = ftFace->glyph->metrics.width >> 6;

	iAdvY[iIndex] = (ftFace->glyph->metrics.height - ftFace->glyph->metrics.horiBearingY) >> 6;
	iBearingY[iIndex] = ftFace->glyph->metrics.horiBearingY >> 6;
	iCharHeight[iIndex] = ftFace->glyph->metrics.height >> 6;

	iNewLine = std::max(iNewLine, int(ftFace->glyph->metrics.height >> 6));

	// Rendering data, texture coordinates are always the same, so now we waste a little memory
	uvs.push_back(Vector2(1,1)); 
	vertices.push_back(Vector3(float(iTW), float(-iAdvY[iIndex]+iTH),0.0f));

	uvs.push_back(Vector2(0,1)); 
	vertices.push_back(Vector3(0.0f,float(-iAdvY[iIndex]+iTH),0.0f));

	uvs.push_back(Vector2(1,0)); 
	vertices.push_back(Vector3(float(iTW),0.0f,0.0f));

	uvs.push_back(Vector2(0,0)); 
	vertices.push_back(Vector3(0.0f,0.0f,0.0f));

	delete[] bData;
}

bool Font::loadFont(std::string sFile, int iPXSize)
{
	BOOL bError = FT_Init_FreeType(&ftLib);
	
	bError = FT_New_Face(ftLib, sFile.c_str(), 0, &ftFace);

	if(bError)
	{
		return false;
	}

	FT_Set_Pixel_Sizes(ftFace, iPXSize, iPXSize);
	loadedPixelSize = iPXSize;

	for(int i = 0; i < 128; i++)
	{
		createChar(i);
	}

	bind();

	loaded = true;

	FT_Done_Face(ftFace);
	FT_Done_FreeType(ftLib);
	
	return true;
}

void Font::print(std::string text, int x, int y, float z, int iPXSize, Colour colour, Matrix4 view, Matrix4 projection)
{
	if(!loaded)
	{
		return;
	}

	shader->begin();

	glUniform4fv((*shader)["colour"],
		4,
		colour.getArray());

	//1st attribute array : vertices
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
	glVertexAttribPointer(
		(*shader)["coord"],	// attribute
		3,							// size
		GL_FLOAT,					// type
		GL_FALSE,					// normalized?
		0,							// stride
		(void*)0					// array buffer offset
	);

	//2nd attribute buffer : uvs		
	glEnableVertexAttribArray(1);
	glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
	glVertexAttribPointer(
		(*shader)["uv"],			// attribute
		2,							// size
		GL_FLOAT,					// type
		GL_FALSE,					// normalized?
		0,							// stride
		(void*)0					// array buffer offset
	);

	int iCurX = x, iCurY = y;
	if(iPXSize == -1)
	{
		iPXSize = loadedPixelSize;
	}

	float fScale = float(iPXSize)/float(loadedPixelSize);

	for(unsigned int i = 0; i < text.length(); i++)
	{
		if(text[i] == '\n')
		{
			iCurX = x;
			iCurY -= iNewLine*iPXSize/loadedPixelSize;
			continue;
		}

		int iIndex = int(text[i]);
		iCurX += iBearingX[iIndex]*iPXSize/loadedPixelSize;

		if(text[i] != ' ')
		{
			//(*shader)["texture"],0
			tCharTextures[iIndex].bindTexture();
			//glUniform1i((*shader)["gSampler"], 0);

			Matrix4 local = Matrix4::toMatrix4(Quaternion(1,0,0,0),Vector3(float(iCurX),float(iCurY),-(z)),fScale);
			Matrix4 world = view * local;
			Matrix4 mvp = projection * world;
			glUniformMatrix4fv((*shader)["mvp"], 1, GL_FALSE, mvp.transpose().getArray());

			// Draw character
			glDrawArrays(GL_TRIANGLE_STRIP, iIndex*4, 4);
		}

		iCurX += (iAdvX[iIndex]-iBearingX[iIndex])*iPXSize/loadedPixelSize;
	}

	shader->end();
}

void Font::releaseFont()
{
	for(int i = 0; i < 128; i++)
	{
		tCharTextures[i].releaseTexture();
	}

	glDeleteBuffers(1, &vertexbuffer);
	glDeleteBuffers(1, &uvbuffer);
}